<!DOCTYPE html><html lang="zh"><head><meta name="generator" content="Hexo 3.8.0"><meta charset="utf-8"><title>Spring Boot 2.0源码解析-配置绑定 - i蝸居年華_谢谢谢_CODE_HOME</title><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1"><meta name="description" content="[TOC] 一. 前言 开发中, 时常会有获取某个属性资源文件的场景, 尤其是在多个Profile不同配置时"><meta name="keywords" content="源码分析,Spring全家桶,Spring Boot"><meta property="og:type" content="article"><meta property="og:title" content="Spring Boot 2.0源码解析-配置绑定"><meta property="og:url" content="https://xiefayang.com/2019/04/01/Spring Boot 配置绑定源码解析/index.html"><meta property="og:site_name" content="i蝸居年華_谢谢谢_CODE_HOME"><meta property="og:description" content="[TOC] 一. 前言 开发中, 时常会有获取某个属性资源文件的场景, 尤其是在多个Profile不同配置时"><meta property="og:locale" content="zh-CN"><meta property="og:image" content="https://xiefayang.com/thumbnails/springboot.png"><meta property="og:updated_time" content="2019-05-21T03:18:31.173Z"><meta name="twitter:card" content="summary"><meta name="twitter:title" content="Spring Boot 2.0源码解析-配置绑定"><meta name="twitter:description" content="[TOC] 一. 前言 开发中, 时常会有获取某个属性资源文件的场景, 尤其是在多个Profile不同配置时"><meta name="twitter:image" content="https://xiefayang.com/thumbnails/springboot.png"><link rel="icon" href="/images/favicon.svg"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.7.2/css/bulma.css"><link rel="stylesheet" href="https://cdn.bootcss.com/font-awesome/5.4.1/css/all.css"><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Ubuntu:400,600|Source+Code+Pro"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/highlight.js@9.12.0/styles/atom-one-dark.css"><style>body>.footer,body>.navbar,body>.section{opacity:0}</style><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/lightgallery@1.6.8/dist/css/lightgallery.min.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/justifiedGallery@3.7.0/dist/css/justifiedGallery.min.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/outdatedbrowser@1.1.5/outdatedbrowser/outdatedbrowser.min.css"><link rel="stylesheet" href="/css/back-to-top.css"><script>var _hmt=_hmt||[];!function(){var e=document.createElement("script");e.src="//hm.baidu.com/hm.js?b59777544dd5d59ce94e191bac62427c";var t=document.getElementsByTagName("script")[0];t.parentNode.insertBefore(e,t)}()</script><link rel="stylesheet" href="/css/progressbar.css"><script src="https://cdn.jsdelivr.net/npm/pace-js@1.0.2/pace.min.js"></script><script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script><link rel="stylesheet" href="/css/style.css"></head><body class="is-3-column"><nav class="navbar navbar-main"><div class="container"><div class="navbar-brand is-flex-center"> <a class="navbar-item navbar-logo" href="/"><img src="/images/maotouying.svg" alt="Spring Boot 2.0源码解析-配置绑定" height="28"> <span style="font-size:16px;display:inline-block;vertical-align:top;font-weight:600">i蝸居年華_谢谢谢</span></a></div><div class="navbar-menu"><div class="navbar-start"> <a class="navbar-item" href="/">首页</a> <a class="navbar-item" href="/archives">归档</a> <a class="navbar-item" href="/categories">分类</a> <a class="navbar-item" href="/tags">标签</a> <a class="navbar-item" href="/about">关于</a></div><div class="navbar-end"><a class="navbar-item is-hidden-tablet catalogue" title="文章目录" href="javascript:;"><i class="fas fa-list-ul"></i></a><a class="navbar-item search" title="搜索" href="javascript:;"><div class="box01"> <input type="text" id="tip" placeholder="You Know, for Search~"><i class="search_icon"></i></div></a></div></div></div></nav><section class="section"><div class="container"><div class="columns"><div class="column is-8-tablet is-8-desktop is-9-widescreen has-order-2 column-main"><div class="card"><div class="card-content article"><div class="level article-meta is-size-7 is-uppercase is-mobile is-overflow-x-auto"><div class="level-left"> <span style="color:#7a7a7a!important">写于2019-04-01 17:39:04&nbsp;&nbsp;&nbsp;</span><div class="level-item"> <a class="has-link-grey -link" href="/categories/Spring-全家桶/">Spring 全家桶</a>&nbsp;/&nbsp;<a class="has-link-grey -link" href="/categories/Spring-全家桶/Spring-Boot-系列/">Spring Boot 系列</a></div> <span class="level-item has-text-grey">32 分钟 读完 (大约 4874 个字)</span><span class="level-item has-text-grey" id="busuanzi_container_page_pv"><i class="far fa-eye"></i> <span id="busuanzi_value_page_pv">0</span>次访问</span></div></div><h1 class="title is-size-3 is-size-4-mobile has-text-weight-normal"> Spring Boot 2.0源码解析-配置绑定</h1><div class="content"><p>[TOC]</p><h3 id="一-前言"><a href="#一-前言" class="headerlink" title="一. 前言"></a>一. 前言</h3><blockquote><p>开发中, 时常会有获取某个属性资源文件的场景, 尤其是在多个Profile不同配置时</p></blockquote><a id="more"></a><p>熟悉Spring的可能知道:</p><ul><li>来自spring-beans的<code>@Value</code>能够完成这一功能</li><li>我们经常在xml中配置的<code>PropertyPlaceholderConfigurer</code>及其父类就是来负责解析属性的</li></ul><blockquote><p>想要了解它的解析过程, 可以从<code>PropertyPlaceholderConfigurer#postProcessBeanFactory()</code>入手</p></blockquote><p><code>@Value</code>用起来很方便, 而在Spring Boot中, 提供了一种更为优雅的配置绑定方式: <code>@ConfigurationProperties</code></p><p>通过配合该注解来完成属性资源文件到Bean属性的绑定.</p><p>之前我很好奇为什么引入rabbit依赖后, 没有配置任何属性源而直接启动就能连接到, Spring Boot的Auto Configuration特性是怎么把连接的配置属性绑定进去的呢</p><p>在<code>spring-boot-autoconfigure</code>源码中, 能看到大量<code>@ConfigurationProperties</code>案例的运用, 例如我们熟悉的:</p><ul><li><code>RedisProperties</code></li><li><code>JpaProperties</code></li><li><code>RabbitProperties</code></li><li>etc…</li></ul><p>这次源码分析我选用的版本是Spring Boot 2.0.8.RELEASE, 而网上的源码分析大都是基于1.x版本</p><h3 id="二-变化"><a href="#二-变化" class="headerlink" title="二. 变化"></a>二. 变化</h3><h4 id="迁移的变化"><a href="#迁移的变化" class="headerlink" title="迁移的变化"></a>迁移的变化</h4><p>在Spring Boot 2.x中, 对配置属性绑定有了一些变化, 例如<strong>Relaxed Binding</strong>(宽松绑定)的约束, 以及新的Binder API</p><p>关于Relaxed Binding, 如果是1.x迁移2.x, 这部分内容的变化并不会造成很大麻烦</p><p>但如果你之前的配置使用了驼峰或过于”宽松”的写法, 那在初始化时校验就会收到报错的, 不信可以试试</p><p>具体变化可以参见wiki: <a href="https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Release-Notes" target="_blank" rel="noopener">Configuration Property Binding</a></p><h4 id="源码的变化"><a href="#源码的变化" class="headerlink" title="源码的变化"></a>源码的变化</h4><p>除了功能方面, 相较1.x版本, 代码部分在2.x有了较大的重构, 重写了整个绑定过程. 个人感觉相比1.x好看了</p><p>接口和绑定阶段职责的变化后面的源码部分会体现</p><p>有意思的是代码风格也有很大变化:</p><ul><li>很多地方都可以看到用Stream流编程和链式的写法, 怪不得要以Java 8为基线</li><li>不少地方都使用了Java 8中的函数接口</li></ul><p>所以看Spring Boot 2.x/Spring Framework 5的源码需要对函数式编程, lambda, Stream流有一些了解</p><blockquote><p>Tip: 需要注意的是在进行源码调试中, 可能会有些不便</p></blockquote><p>比如Stream的惰性求值特性, 也就是中间操作会在终止条件调用时候才执行</p><p>后面源码中能看到return结果中的<code>allMatch()</code>和<code>findFirst()</code>就是终止条件中的短路操作!</p><p>好在我们可以善用IDE的Evaluate Expression功能来查看</p><p>包括调试中, 如果我们想只关注自己配置的属性, 也可以使用IDE的debug condition功能, 很方便!</p><h3 id="三-基本使用"><a href="#三-基本使用" class="headerlink" title="三. 基本使用"></a>三. 基本使用</h3><p><code>@ConfigurationProperties</code>的使用极为简单, 以下三步</p><ul><li><p>属性配置文件</p><figure class="highlight yaml hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-attr">custom:</span></span><br><span class="line"><span class="hljs-attr">  name:</span> <span class="hljs-string">thank</span></span><br><span class="line"><span class="hljs-attr">  age:</span> <span class="hljs-number">18</span></span><br><span class="line">  <span class="hljs-string">address[0]:</span> <span class="hljs-string">上海</span></span><br><span class="line">  <span class="hljs-string">address[1]:</span> <span class="hljs-string">北京</span></span><br></pre></td></tr></table></figure></li><li><p>属性配置类</p><figure class="highlight java hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-meta">@Data</span></span><br><span class="line"><span class="hljs-meta">@Component</span></span><br><span class="line"><span class="hljs-meta">@ConfigurationProperties</span>(prefix = <span class="hljs-string">"custom"</span>)</span><br><span class="line"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CustomProperties</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">  <span class="hljs-keyword">private</span> String name;</span><br><span class="line"></span><br><span class="line">  <span class="hljs-keyword">private</span> Integer age;</span><br><span class="line"></span><br><span class="line">  <span class="hljs-keyword">private</span> List&lt;String&gt; address;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>简单验证</p><figure class="highlight java hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-meta">@Test</span></span><br><span class="line"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">testBean</span><span class="hljs-params">()</span> </span>&#123;</span><br><span class="line">    ConfigurableApplicationContext applicationContext </span><br><span class="line">        = SpringApplication.run(CacheApplication.class);</span><br><span class="line">    CustomProperties bean = applicationContext.getBean(CustomProperties.class);</span><br><span class="line">    System.out.println(bean.toString());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ul><h3 id="四-源码解析"><a href="#四-源码解析" class="headerlink" title="四. 源码解析"></a>四. 源码解析</h3><p>源码重点关注的部分是spring-boot-xx模块的, 涉及到Spring Framework部分的源码则不做太多说明</p><h4 id="ConfigurationProperties"><a href="#ConfigurationProperties" class="headerlink" title="@ConfigurationProperties"></a>@ConfigurationProperties</h4><p>首先来看下<code>@ConfigurationProperties</code>的源码, 我去掉了注释部分</p><figure class="highlight java hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-meta">@Target</span>(&#123; ElementType.TYPE, ElementType.METHOD &#125;)</span><br><span class="line"><span class="hljs-meta">@Retention</span>(RetentionPolicy.RUNTIME)</span><br><span class="line"><span class="hljs-meta">@Documented</span></span><br><span class="line"><span class="hljs-keyword">public</span> <span class="hljs-meta">@interface</span> ConfigurationProperties &#123;</span><br><span class="line"></span><br><span class="line">   <span class="hljs-meta">@AliasFor</span>(<span class="hljs-string">"prefix"</span>)</span><br><span class="line">   <span class="hljs-function">String <span class="hljs-title">value</span><span class="hljs-params">()</span> <span class="hljs-keyword">default</span> ""</span>;</span><br><span class="line"></span><br><span class="line">   <span class="hljs-meta">@AliasFor</span>(<span class="hljs-string">"value"</span>)</span><br><span class="line">   <span class="hljs-function">String <span class="hljs-title">prefix</span><span class="hljs-params">()</span> <span class="hljs-keyword">default</span> ""</span>;</span><br><span class="line"></span><br><span class="line">   <span class="hljs-function"><span class="hljs-keyword">boolean</span> <span class="hljs-title">ignoreInvalidFields</span><span class="hljs-params">()</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">false</span></span>;</span><br><span class="line"></span><br><span class="line">   <span class="hljs-function"><span class="hljs-keyword">boolean</span> <span class="hljs-title">ignoreUnknownFields</span><span class="hljs-params">()</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">true</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>简单说明:</p><ul><li><p><code>value() &amp; prefiex()</code>: 前面示例已经使用过, 用来解析属性文件中的前缀</p><p>可以看到上面打有<code>@AliasFor</code>, 所以你用哪个都没问题</p></li><li><p><code>ignoreInvalidFields()</code>: 是否忽略无效的字段, 默认为false</p><p>e.g. 有一个<code>age=xxx</code>的配置, 无法将该值绑定到Bean中的<code>Integer age</code>上, 是选择忽略(true)还是抛出异常(false)</p></li><li><p><code>ignoreUnknownFields()</code>: 是否忽略未知的字段, 默认为true</p><p>e.g. 有一个在Bean中不存在的属性, 是选择忽略(true)还是抛出异常(false)</p></li></ul><p>使用方式可以说简单了, 那么会有两个疑问:</p><ul><li><strong>Spring是如何发现我们打有<code>@ConfigurationProperties</code>注解的bean的?</strong></li><li><strong>是如何将属性资源文件中的值绑定到Bean中的?</strong></li></ul><h4 id="ConfigurationPropertiesBindingPostProcessor"><a href="#ConfigurationPropertiesBindingPostProcessor" class="headerlink" title="ConfigurationPropertiesBindingPostProcessor"></a>ConfigurationPropertiesBindingPostProcessor</h4><p>在<code>@ConfigurationProperties</code>源码上可以看到@see中标明了以下两个类:</p><figure class="highlight java hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-meta">@see</span> ConfigurationPropertiesBindingPostProcessor</span><br><span class="line"><span class="hljs-meta">@see</span> EnableConfigurationProperties</span><br></pre></td></tr></table></figure><p>一个处理类和一个Enable模块中的配置属性绑定模块注解</p><p>从字面意思看, 我们应该从这个处理类去寻找答案</p><p>进入该处理类<code>ConfigurationPropertiesBindingPostProcessor</code>来看看它的图:</p><p><img src="https://blog-md-pic-1259135436.cos.ap-chengdu.myqcloud.com/Spring%E5%85%A8%E5%AE%B6%E6%A1%B6/%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/ConfigurationPropertiesBindingPostProcessor.png" alt="ConfigurationPropertiesBindingPostProcessor.png"></p><p>相比Spring Boot 1.x版本, 继承结构简化清晰了好多</p><p>看到了几个常见接口, 能大概挑一下非重点:</p><ul><li><p>ApplicationContextAware: 方便获取上应用下文信息</p></li><li><p>Ordered: 方法<code>getOrder()</code>指定优先级, 可以理解为用来处理处理器的调用顺序</p></li></ul><p>重点是看到了BeanPostProcessor和InitializingBean, 一定能猜到容器bean初始化实例时会调用一个初始方法, 和另外两个前后回调方法</p><p>所以重点关注以下三个Override方法:</p><ul><li>BeanPostProcessor: <code>postProcessBeforeInitialization()</code></li><li>InitializingBean: <code>afterPropertiesSet()</code></li><li><del>BeanPostProcessor: postProcessAfterInitialization()</del></li></ul><p>这里与Spring 4.x不同的是在5.x中<code>BeanPostProcessor</code>的两个回调方法声明都使用了default method,</p><p>所以在这里<code>postProcessAfterInitialization()</code>也就是后置并未做任何事情, 可以忽略</p><h5 id="afterPropertiesSet"><a href="#afterPropertiesSet" class="headerlink" title="afterPropertiesSet()"></a>afterPropertiesSet()</h5><p>源码如下:<br></p><figure class="highlight java hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-meta">@Override</span></span><br><span class="line"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">afterPropertiesSet</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception </span>&#123;</span><br><span class="line">    <span class="hljs-keyword">this</span>.beanFactoryMetadata = <span class="hljs-keyword">this</span>.applicationContext.getBean(</span><br><span class="line">        ConfigurationBeanFactoryMetadata.BEAN_NAME, </span><br><span class="line">        ConfigurationBeanFactoryMetadata.class</span><br><span class="line">    );</span><br><span class="line">    <span class="hljs-keyword">this</span>.configurationPropertiesBinder = </span><br><span class="line">        <span class="hljs-keyword">new</span> ConfigurationPropertiesBinder(<span class="hljs-keyword">this</span>.applicationContext, VALIDATOR_BEAN_NAME);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p></p><p>可以看到这里做了两个初始化:</p><ul><li><p><code>beanFactoryMetadata</code>: 初始化了工厂Bean的元数据信息</p></li><li><p><code>configurationPropertiesBinder</code>: 从字面翻译感觉它一定与配置属性绑定有关</p><p>该类在1.x版本是没有的, 但原理类似</p></li></ul><p>此时需要看一眼<code>ConfigurationPropertiesBinder</code>的构造函数</p><figure class="highlight java hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">ConfigurationPropertiesBinder(ApplicationContext applicationContext, String validatorBeanName) &#123;</span><br><span class="line">    <span class="hljs-keyword">this</span>.applicationContext = applicationContext;</span><br><span class="line">    <span class="hljs-keyword">this</span>.propertySources = <span class="hljs-keyword">new</span> PropertySourcesDeducer(applicationContext).getPropertySources();</span><br><span class="line">    <span class="hljs-keyword">this</span>.configurationPropertiesValidator = </span><br><span class="line">        getConfigurationPropertiesValidator(applicationContext, validatorBeanName);</span><br><span class="line">    <span class="hljs-keyword">this</span>.jsr303Present = </span><br><span class="line">        ConfigurationPropertiesJsr303Validator.isJsr303Present(applicationContext);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到只是将<code>propertySources</code>, <code>validator</code>封装在内</p><h5 id="关于validator"><a href="#关于validator" class="headerlink" title="关于validator"></a>关于validator</h5><p>这里看到一个jsr303XXX, 如果不清楚JSR-303, 但是你一定熟悉Hibernate-validator</p><p>不要跟ORM产生联系, 其是JSR-303规范的实现</p><p>所以也能大概明白它的作用: 即在绑定阶段对<code>@ConfigurationProperties</code>Bean中的属性做校验</p><p>前面的简单示例中没有加valid, 其实用法与我们以前校验Form实体没什么两样, 在配置属性中标记valid注解即可:</p><figure class="highlight java hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-meta">@Data</span></span><br><span class="line"><span class="hljs-meta">@Component</span></span><br><span class="line"><span class="hljs-meta">@Validated</span></span><br><span class="line"><span class="hljs-meta">@ConfigurationProperties</span>(value = <span class="hljs-string">"custom"</span>)</span><br><span class="line"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CustomProperties</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="hljs-keyword">private</span> String name;</span><br><span class="line">    </span><br><span class="line">    <span class="hljs-meta">@Min</span>(<span class="hljs-number">0</span>) <span class="hljs-meta">@Max</span>(<span class="hljs-number">30</span>)</span><br><span class="line">    <span class="hljs-keyword">private</span> Integer age;</span><br><span class="line">    </span><br><span class="line">    <span class="hljs-meta">@Email</span>(message = <span class="hljs-string">"Not Email!"</span>)</span><br><span class="line">    <span class="hljs-keyword">private</span> String email;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>需要注意的是, 2.x的@Valid在hibernate-validator已经被标记为<code>@Deprecated</code>, 用javax.validation的吧!</p></blockquote><p>校验部分与配置属性绑定并无太大联系, 在此时不用太关注, 所以后面Validation相关的源码就刻意跳过!</p><h5 id="关于propertySources"><a href="#关于propertySources" class="headerlink" title="关于propertySources"></a>关于propertySources</h5><p>看到<code>propertySources</code>是不是很眼熟? 以及<code>Environment</code></p><p>这两个spring-core中提供的接口及其实现完成了系统属性, 环境配置, 属性资源配置的解析</p><p>可以在<code>propertySources</code>构造完成后打一个断点观察下</p><p>如图: 它正确的加载了默认的配置文件<code>application.yml</code>及我们定义的几个属性<code>custom.xxx</code></p><p><img src="https://blog-md-pic-1259135436.cos.ap-chengdu.myqcloud.com/Spring%E5%85%A8%E5%AE%B6%E6%A1%B6/%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/propertySource.png" alt="propertySource.png"></p><p>当然, 可以结合<code>@PropertySources</code>自己指定资源文件的位置. 这仍是spring-core的东西, 不说了</p><h5 id="postProcessBeforeInitialization"><a href="#postProcessBeforeInitialization" class="headerlink" title="postProcessBeforeInitialization()"></a>postProcessBeforeInitialization()</h5><p>看完<code>afterPropertiesSet()</code>, 按照顺序, 自然来到了<code>postProcessBeforeInitialization()</code>中</p><figure class="highlight java hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-meta">@Override</span></span><br><span class="line"><span class="hljs-function"><span class="hljs-keyword">public</span> Object <span class="hljs-title">postProcessBeforeInitialization</span><span class="hljs-params">(Object bean, String beanName)</span> </span></span><br><span class="line"><span class="hljs-function">    <span class="hljs-keyword">throws</span> BeansException </span>&#123;</span><br><span class="line">    ConfigurationProperties annotation = getAnnotation(</span><br><span class="line">        bean, beanName, ConfigurationProperties.class</span><br><span class="line">    );</span><br><span class="line">    <span class="hljs-keyword">if</span> (annotation != <span class="hljs-keyword">null</span>) &#123;</span><br><span class="line">        bind(bean, beanName, annotation);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="hljs-keyword">return</span> bean;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>该方法很简单, 就是调用一个<code>getAnnotation()</code>方法, 找出有<code>ConfigurationProperties</code>声明的bean, 执行<code>bind()</code>操作</p><p>该部分处理一定是被循环调用的, 具体的调用时机在这里:</p><p><code>AbstractAutowireCapableBeanFactory#initializeBean</code> –&gt; <code>applyBeanPostProcessorsBeforeInitialization()</code></p><p>回到<code>getAnnotation()</code>方法, 它的源码如下:<br></p><figure class="highlight java hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-keyword">private</span> &lt;A extends Annotation&gt; <span class="hljs-function">A <span class="hljs-title">getAnnotation</span><span class="hljs-params">(Object bean, String beanName, Class&lt;A&gt; type)</span> </span>&#123;</span><br><span class="line">    A annotation = <span class="hljs-keyword">this</span>.beanFactoryMetadata.findFactoryAnnotation(beanName, type);</span><br><span class="line">    <span class="hljs-keyword">if</span> (annotation == <span class="hljs-keyword">null</span>) &#123;</span><br><span class="line">        annotation = AnnotationUtils.findAnnotation(bean.getClass(), type);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="hljs-keyword">return</span> annotation;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p></p><p>可以看到会先从工厂bean中开始寻找起, 看到<code>beanFactoryMetadata</code>了吗, 前面提到过</p><p>在<code>ConfigurationBeanFactoryMetadata</code>中有一个这样的集合:</p><ul><li><code>Map&lt;String, FactoryMetadata&gt; beansFactoryMetadata</code></li></ul><p>在回调方法<code>postProcessBeanFactory()</code>中, 完成对配置元数据进行处理, 这里的处理仅是对那个Map做put操作</p><p>无论是否工厂方式创建的bean, 最终都会调用工具类<code>AnnotationUtils</code>, 反射得到注解<code>ConfigurationProperties</code>信息</p><p>Then return it</p><p>基于以上, Spring已经找到了标记<code>@ConfigurationProperties</code>的配置bean了</p><p>但是还没有发现属性绑定阶段相关的信息</p><h5 id="bind"><a href="#bind" class="headerlink" title="bind()"></a>bind()</h5><p>接着往下看, 进入<code>bind()</code>, 猜想它一定负责绑定阶段</p><figure class="highlight java hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">bind</span><span class="hljs-params">(Object bean, String beanName, ConfigurationProperties annotation)</span> </span>&#123;</span><br><span class="line">    ResolvableType type = getBeanType(bean, beanName);</span><br><span class="line">    Validated validated = getAnnotation(bean, beanName, Validated.class);</span><br><span class="line">    Annotation[] annotations = (validated != <span class="hljs-keyword">null</span>)</span><br><span class="line">            ? <span class="hljs-keyword">new</span> Annotation[] &#123; annotation, validated &#125;</span><br><span class="line">            : <span class="hljs-keyword">new</span> Annotation[] &#123; annotation &#125;;</span><br><span class="line">    Bindable&lt;?&gt; target = Bindable.of(type)</span><br><span class="line">        .withExistingValue(bean).withAnnotations(annotations);</span><br><span class="line">    <span class="hljs-keyword">try</span> &#123;</span><br><span class="line">        <span class="hljs-keyword">this</span>.configurationPropertiesBinder.bind(target);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="hljs-keyword">catch</span> (Exception ex) &#123;</span><br><span class="line">        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> ConfigurationPropertiesBindException(beanName, bean, annotation, ex);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>同样, 略过Validate的部分, 可以看到使用链式设置属性包装出一个<code>Bindable</code>对象, 包含了以下内容:</p><ul><li><p>type: 使用Spring提供的泛型操作API获取到该bean的泛型信息</p><p>后面的绑定阶段需要根据属性的类型来判断使用什么方式转换(e.g. String, Collections…)</p></li><li><p>bean: 该bean, 例如我们自己配置的<code>customProperties</code></p></li><li><p>annotations: 包含注解<code>@ConfigurationProperties</code>和<code>@Validated</code>的信息</p></li></ul><p>进入<code>ConfigurationPropertiesBinder#bind(Bindable&lt;?&gt; target)</code><br></p><figure class="highlight java hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">bind</span><span class="hljs-params">(Bindable&lt;?&gt; target)</span> </span>&#123;</span><br><span class="line">    ConfigurationProperties annotation = target.getAnnotation(ConfigurationProperties.class);</span><br><span class="line">    Assert.state(annotation != <span class="hljs-keyword">null</span>, () -&gt; <span class="hljs-string">"Missing @ConfigurationProperties on "</span> + target);</span><br><span class="line">    List&lt;Validator&gt; validators = getValidators(target);</span><br><span class="line">    BindHandler bindHandler = getBindHandler(annotation, validators); <span class="hljs-comment">// 看这里</span></span><br><span class="line">    getBinder().bind(annotation.prefix(), target, bindHandler); <span class="hljs-comment">// 看这里</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p></p><p>这里的<code>getBindHandler()</code>, 字面意思是返回一个绑定的处理器</p><p>进入源码可以看到, 就是根据注解<code>ConfigurationProperties</code>里前面说过的那两个属性来选择的:</p><ul><li>IgnoreErrorsBindHandler: 设置<code>ignoreInvalidFields = true</code>时: 忽略无效的属性</li><li>NoUnboundElementsBindHandler: <code>ignoreUnknownFields = false</code>时: 忽略不存在的的属性</li><li>IgnoreTopLevelConverterNotFoundBindHandler: 默认情况使用的</li></ul><p>除了以上三个, 你可能还会看见<code>ValidationBindHandler</code>, 是对绑定对象执行Valid的处理器, 同样略过</p><p>查看这几个处理器的继承结构可以看出, 他们有共同的接口<code>BindHandler</code></p><p>其中的不同主要体现在: onStart, onSuccess, onFailure, onFinish这几个阶段的回调方法上不同的覆盖行为上</p><h5 id="getBinder"><a href="#getBinder" class="headerlink" title="getBinder()"></a>getBinder()</h5><p>接下来看到<code>getBinder()</code>, 还记得之前的<code>afterPropertiesSet()</code>中做了哪些内容的初始化吧</p><p>结合它的构造函数, 来看下<code>Binder</code>是怎么构造出来的<br></p><figure class="highlight java hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-function"><span class="hljs-keyword">private</span> Binder <span class="hljs-title">getBinder</span><span class="hljs-params">()</span> </span>&#123;</span><br><span class="line">    <span class="hljs-keyword">if</span> (<span class="hljs-keyword">this</span>.binder == <span class="hljs-keyword">null</span>) &#123;</span><br><span class="line">        <span class="hljs-keyword">this</span>.binder = <span class="hljs-keyword">new</span> Binder(</span><br><span class="line">                getConfigurationPropertySources(),</span><br><span class="line">                getPropertySourcesPlaceholdersResolver(), </span><br><span class="line">                getConversionService(),</span><br><span class="line">                getPropertyEditorInitializer());</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.binder;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p></p><p>至此, 我们见到了<code>Binder</code>, <code>Bindable</code>, 接下来还会见到<code>BindResult</code>, 这都是Spring Boot 2.x中Binder API中的东西</p><p>所以对比过1.x源码你会发现, 这里是最大的变化</p><blockquote><p>文档中说明了: 一个<code>Binder</code>采用一个<code>Bindable</code>并返回一个<code>BindResult</code></p></blockquote><p>接下来你也会发现, <code>Bindable</code>在整个绑定过程中, 贯穿始终!</p><p>回到这个<code>getBinder()</code>方法, 解释下涉及到的几个接口:</p><ul><li><p><code>ConfigurationPropertySources</code>: 这也是Spring Boot 2.x中加入的, 所以相较之前版本, 2.x开始使用<code>ConfigurationPropertySource</code>作为配置属性源进行属性绑定, 而非之前的<code>propertySources</code></p><p>通过前面携带配置属性信息的<code>propertySources</code>对象而来<br>它返回的是一个Iterable container, 通过注释也说明了, 他会展开嵌套的属性</p></li><li><p><code>ConversionService</code>: 这也是spring-core中的, 很容易想到, 在对资源文件和属性bean之间绑定属性时需要这样一个提供类型转换功能的转换器</p></li></ul><p>大致看一下:<br></p><figure class="highlight java hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line">  <span class="hljs-comment">/**</span></span><br><span class="line"><span class="hljs-comment"> * Add converters useful for most Spring Boot applications.</span></span><br><span class="line"><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> registry the registry of converters to add to (must also be castable to</span></span><br><span class="line"><span class="hljs-comment"> * ConversionService, e.g. being a &#123;<span class="hljs-doctag">@link</span> ConfigurableConversionService&#125;)</span></span><br><span class="line"><span class="hljs-comment"> * <span class="hljs-doctag">@throws</span> ClassCastException if the given ConverterRegistry could not be cast to a</span></span><br><span class="line"><span class="hljs-comment"> * ConversionService</span></span><br><span class="line"><span class="hljs-comment"> */</span></span><br><span class="line"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">addApplicationConverters</span><span class="hljs-params">(ConverterRegistry registry)</span> </span>&#123;</span><br><span class="line">    addDelimitedStringConverters(registry);</span><br><span class="line">    registry.addConverter(<span class="hljs-keyword">new</span> StringToDurationConverter());</span><br><span class="line">    registry.addConverter(<span class="hljs-keyword">new</span> DurationToStringConverter());</span><br><span class="line">    registry.addConverter(<span class="hljs-keyword">new</span> NumberToDurationConverter());</span><br><span class="line">    registry.addConverter(<span class="hljs-keyword">new</span> DurationToNumberConverter());</span><br><span class="line">    registry.addConverterFactory(<span class="hljs-keyword">new</span> StringToEnumIgnoringCaseConverterFactory());</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="hljs-comment">/**</span></span><br><span class="line"><span class="hljs-comment"> * Add converters to support delimited strings.</span></span><br><span class="line"><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> registry the registry of converters to add to (must also be castable to</span></span><br><span class="line"><span class="hljs-comment"> * ConversionService, e.g. being a &#123;<span class="hljs-doctag">@link</span> ConfigurableConversionService&#125;)</span></span><br><span class="line"><span class="hljs-comment"> * <span class="hljs-doctag">@throws</span> ClassCastException if the given ConverterRegistry could not be cast to a</span></span><br><span class="line"><span class="hljs-comment"> * ConversionService</span></span><br><span class="line"><span class="hljs-comment"> */</span></span><br><span class="line"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">addDelimitedStringConverters</span><span class="hljs-params">(ConverterRegistry registry)</span> </span>&#123;</span><br><span class="line">    ConversionService service = (ConversionService) registry;</span><br><span class="line">    registry.addConverter(<span class="hljs-keyword">new</span> ArrayToDelimitedStringConverter(service));</span><br><span class="line">    registry.addConverter(<span class="hljs-keyword">new</span> CollectionToDelimitedStringConverter(service));</span><br><span class="line">    registry.addConverter(<span class="hljs-keyword">new</span> DelimitedStringToArrayConverter(service));</span><br><span class="line">    registry.addConverter(<span class="hljs-keyword">new</span> DelimitedStringToCollectionConverter(service));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p></p><p><img src="https://blog-md-pic-1259135436.cos.ap-chengdu.myqcloud.com/Spring%E5%85%A8%E5%AE%B6%E6%A1%B6/%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/propertySource.png" alt="converters_debug"></p><p>最终会调用多个类型转换服务</p><h4 id="Binder"><a href="#Binder" class="headerlink" title="Binder"></a>Binder</h4><p>接下来, 可以进入<code>Binder#bind()</code>了</p><h5 id="bind-1"><a href="#bind-1" class="headerlink" title="bind()"></a>bind()</h5><p>里面的构造函数很多, 我们着重来看这个<br></p><figure class="highlight java hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-keyword">public</span> &lt;T&gt; <span class="hljs-function">BindResult&lt;T&gt; <span class="hljs-title">bind</span><span class="hljs-params">(ConfigurationPropertyName name, Bindable&lt;T&gt; target, BindHandler handler)</span> </span>&#123;</span><br><span class="line">    Assert.notNull(name, <span class="hljs-string">"Name must not be null"</span>);</span><br><span class="line">    Assert.notNull(target, <span class="hljs-string">"Target must not be null"</span>);</span><br><span class="line">    handler = (handler != <span class="hljs-keyword">null</span>) ? handler : BindHandler.DEFAULT;</span><br><span class="line">    Context context = <span class="hljs-keyword">new</span> Context();</span><br><span class="line">    T bound = bind(name, target, handler, context, <span class="hljs-keyword">false</span>);</span><br><span class="line">    <span class="hljs-keyword">return</span> BindResult.of(bound);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p></p><p>这里还不是真正的绑定阶段, 但有两个点需要说明:</p><ul><li><p>参数: <code>ConfigurationPropertyName</code>: 是对前面传入的配置前缀<code>prefix</code>进行一些基本校验和处理</p><p>e.g.分割<code>.</code>连接的前缀</p><blockquote><p>注释中也提醒了: <code>they must be lower-case and must start with an alpha-numeric character</code></p></blockquote></li><li><p>返回值: <code>BindResult</code>: 可以简单的看下它的源码, 里面有<code>of()</code>, <code>get()</code>, <code>isBound()</code>, <code>orElse()</code>等</p><p>是不是想起了Guava或Java 8中的<code>Optional&lt;T&gt;</code>, 没错, 他们的作用基本一样, 强制让调用方考虑处理绑定结果为<code>null</code>的情况</p></li><li><p><code>Context</code>: 在这里是绑定上下文, 由前面说过的<code>BindHandler</code>使用</p><figure class="highlight java hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">BindContext</span> </span>&#123;</span><br><span class="line"></span><br><span class="line"> <span class="hljs-comment">/**</span></span><br><span class="line"><span class="hljs-comment">  * Return the current depth of the binding. Root binding starts with a depth of</span></span><br><span class="line"><span class="hljs-comment">  * &#123;<span class="hljs-doctag">@code</span> 0&#125;. Each subsequent property binding increases the depth by &#123;<span class="hljs-doctag">@code</span> 1&#125;.</span></span><br><span class="line"><span class="hljs-comment">  * <span class="hljs-doctag">@return</span> the depth of the current binding</span></span><br><span class="line"><span class="hljs-comment">    */</span></span><br><span class="line"> <span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">getDepth</span><span class="hljs-params">()</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="hljs-comment">/**</span></span><br><span class="line"><span class="hljs-comment">  * Return an &#123;<span class="hljs-doctag">@link</span> Iterable&#125; of the &#123;<span class="hljs-doctag">@link</span> ConfigurationPropertySource sources&#125; being</span></span><br><span class="line"><span class="hljs-comment">  * used by the &#123;<span class="hljs-doctag">@link</span> Binder&#125;.</span></span><br><span class="line"><span class="hljs-comment">  * <span class="hljs-doctag">@return</span> the sources</span></span><br><span class="line"><span class="hljs-comment">    */</span></span><br><span class="line"> <span class="hljs-function">Iterable&lt;ConfigurationPropertySource&gt; <span class="hljs-title">getSources</span><span class="hljs-params">()</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="hljs-comment">/**</span></span><br><span class="line"><span class="hljs-comment">  * Return the &#123;<span class="hljs-doctag">@link</span> ConfigurationProperty&#125; actually being bound or &#123;<span class="hljs-doctag">@code</span> null&#125; if</span></span><br><span class="line"><span class="hljs-comment">  * the property has not yet been determined.</span></span><br><span class="line"><span class="hljs-comment">  * <span class="hljs-doctag">@return</span> the configuration property (may be &#123;<span class="hljs-doctag">@code</span> null&#125;).</span></span><br><span class="line"><span class="hljs-comment">    */</span></span><br><span class="line"> <span class="hljs-function">ConfigurationProperty <span class="hljs-title">getConfigurationProperty</span><span class="hljs-params">()</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ul><p>这里的绑定上下文中就提供前面说过的ConfigurationPropertySource Iterable Container</p><p>跟着代码往下, 看Binder中的下一个构造函数:<br></p><figure class="highlight java hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">final</span> &lt;T&gt; <span class="hljs-function">T <span class="hljs-title">bind</span><span class="hljs-params">(ConfigurationPropertyName name, Bindable&lt;T&gt; target, </span></span></span><br><span class="line"><span class="hljs-function"><span class="hljs-params">                           BindHandler handler, Context context, <span class="hljs-keyword">boolean</span> allowRecursiveBinding)</span> </span>&#123;</span><br><span class="line">    context.clearConfigurationProperty();</span><br><span class="line">    <span class="hljs-keyword">try</span> &#123;</span><br><span class="line">        <span class="hljs-keyword">if</span> (!handler.onStart(name, target, context)) &#123;</span><br><span class="line">            <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        Object bound = bindObject(name, target, handler, context,allowRecursiveBinding);</span><br><span class="line">        <span class="hljs-keyword">return</span> handleBindResult(name, target, handler, context, bound);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="hljs-keyword">catch</span> (Exception ex) &#123;</span><br><span class="line">        <span class="hljs-keyword">return</span> handleBindError(name, target, handler, context, ex);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p></p><p>看到onStart了吗, 这里就有一个<code>BindHandler</code>回调接口中的一个方法, 也就是绑定开始但还未完成阶段</p><p>接下来在<code>Binder</code>中, 会陆续看到其它几个阶段的方法</p><p>再来看返回, 是通过<code>handleBindResult()</code>和<code>handleBindError()</code>来处理的.</p><p>点开这两个handleBindXXX()能看到在里面进行了onSuccess, onFinish, onFailure的调用, 代码不贴</p><h5 id="bindObject"><a href="#bindObject" class="headerlink" title="bindObject()"></a>bindObject()</h5><p>重点来看<code>bindObject()</code></p><figure class="highlight java hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-keyword">private</span> &lt;T&gt; <span class="hljs-function">Object <span class="hljs-title">bindObject</span><span class="hljs-params">(ConfigurationPropertyName name, Bindable&lt;T&gt; target, BindHandler handler, Context context, <span class="hljs-keyword">boolean</span> allowRecursiveBinding)</span> </span>&#123;</span><br><span class="line">    ConfigurationProperty property = findProperty(name, context); <span class="hljs-comment">// Part 1</span></span><br><span class="line">    <span class="hljs-keyword">if</span> (property == <span class="hljs-keyword">null</span> &amp;&amp; containsNoDescendantOf(context.streamSources(), name)) &#123; <span class="hljs-comment">// Part 1</span></span><br><span class="line">        <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    AggregateBinder&lt;?&gt; aggregateBinder = getAggregateBinder(target, context); </span><br><span class="line">    <span class="hljs-keyword">if</span> (aggregateBinder != <span class="hljs-keyword">null</span>) &#123;</span><br><span class="line">        <span class="hljs-keyword">return</span> bindAggregate(name, target, handler, context, aggregateBinder); <span class="hljs-comment">// Part 2</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="hljs-keyword">if</span> (property != <span class="hljs-keyword">null</span>) &#123;</span><br><span class="line">        <span class="hljs-keyword">try</span> &#123;</span><br><span class="line">            <span class="hljs-keyword">return</span> bindProperty(target, context, property); <span class="hljs-comment">// Part 2</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="hljs-keyword">catch</span> (ConverterNotFoundException ex) &#123;</span><br><span class="line">            <span class="hljs-comment">// We might still be able to bind it as a bean</span></span><br><span class="line">            Object bean = bindBean(name, target, handler, context, allowRecursiveBinding);</span><br><span class="line">            <span class="hljs-keyword">if</span> (bean != <span class="hljs-keyword">null</span>) &#123;</span><br><span class="line">                <span class="hljs-keyword">return</span> bean;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="hljs-keyword">throw</span> ex;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="hljs-keyword">return</span> bindBean(name, target, handler, context, allowRecursiveBinding); <span class="hljs-comment">// Part 2</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里过程比较多, 可以分为2个部分来看</p><h6 id="Part-1-寻找属性和匹配过程"><a href="#Part-1-寻找属性和匹配过程" class="headerlink" title="Part 1: 寻找属性和匹配过程"></a>Part 1: 寻找属性和匹配过程</h6><p>开头的<code>findProperty()</code>和一个匹配方法:<code>containsNoDescendantOf()</code>, 它们的参数都有<code>context</code></p><p>还记得吧? 上面说过了–&gt;提供绑定的上下文信息</p><p>这里在debug时候可以利用IDE的Evaluate Expression功能来验证判断的逻辑:</p><p><img src="https://blog-md-pic-1259135436.cos.ap-chengdu.myqcloud.com/Spring%E5%85%A8%E5%AE%B6%E6%A1%B6/%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/context_stremsource.png" alt="context_streamsource"></p><h6 id="Part-2-绑定过程"><a href="#Part-2-绑定过程" class="headerlink" title="Part 2: 绑定过程"></a>Part 2: 绑定过程</h6><p>接下来是<code>bindXXX</code>这三个私有方法:</p><ul><li><p><code>bindAggregate()</code>: 从注释上看出, 主要是负责处理Map, Collections, Array的绑定策略, 及完成多层属性的递归</p></li><li><p><code>bindProperty()</code>: 是返回属性值的过程(其中包含类型转换)</p><p>例如属性资源文件中配置的<code>name=thank</code> -&gt; <code>java.lang.String thank</code></p></li><li><p><code>bindBean()</code>: 会继续调用另外一个私有的重载函数<code>bindBean()</code></p></li></ul><h5 id="bindBean"><a href="#bindBean" class="headerlink" title="bindBean()"></a>bindBean()</h5><p>重点看看这个方法</p><figure class="highlight java hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-function"><span class="hljs-keyword">private</span> Object <span class="hljs-title">bindBean</span><span class="hljs-params">(ConfigurationPropertyName name, Bindable&lt;?&gt; target, BindHandler handler, Context context, <span class="hljs-keyword">boolean</span> allowRecursiveBinding)</span> </span>&#123;</span><br><span class="line">    <span class="hljs-keyword">if</span> (containsNoDescendantOf(context.streamSources(), name) </span><br><span class="line">        || isUnbindableBean(name, target, context)) &#123;</span><br><span class="line">        <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    BeanPropertyBinder propertyBinder = </span><br><span class="line">        (propertyName, propertyTarget) -&gt; bind(</span><br><span class="line">            name.append(propertyName), propertyTarget, handler, context, <span class="hljs-keyword">false</span>);</span><br><span class="line">    Class&lt;?&gt; type = target.getType().resolve(Object.class);</span><br><span class="line">    <span class="hljs-keyword">if</span> (!allowRecursiveBinding &amp;&amp; context.hasBoundBean(type)) &#123;</span><br><span class="line">        <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="hljs-keyword">return</span> context.withBean(type, () -&gt; &#123;</span><br><span class="line">        Stream&lt;?&gt; boundBeans = BEAN_BINDERS.stream()</span><br><span class="line">            .map((b) -&gt; b.bind(name, target, context, propertyBinder));</span><br><span class="line">        <span class="hljs-keyword">return</span> boundBeans.filter(Objects::nonNull).findFirst().orElse(<span class="hljs-keyword">null</span>);</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>里面又调用了上层的<code>Binder.bind()</code>, 递归完成绑定</p><p>终止条件是上面提到过的<code>containsNoDescendantOf()</code>和另外一个判断<code>isUnbindableBean()</code></p><p>关注一下最终的返回结果, 是调用了另外一个类: <code>JavaBeanBinder</code>的<code>bind</code>方法</p><h4 id="JavaBeanBinder"><a href="#JavaBeanBinder" class="headerlink" title="JavaBeanBinder"></a>JavaBeanBinder</h4><p><code>JavaBeanBinder</code>是哪来的呢? 在<code>Binder</code>的静态方法中, 已经定义好了, 并被添加到一个不可变集合中</p><figure class="highlight java hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Binder</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">   <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> List&lt;BeanBinder&gt; BEAN_BINDERS;</span><br><span class="line"></span><br><span class="line">   <span class="hljs-keyword">static</span> &#123;</span><br><span class="line">      List&lt;BeanBinder&gt; binders = <span class="hljs-keyword">new</span> ArrayList&lt;&gt;();</span><br><span class="line">      binders.add(<span class="hljs-keyword">new</span> JavaBeanBinder());</span><br><span class="line">      BEAN_BINDERS = Collections.unmodifiableList(binders);</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="hljs-comment">//...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="bind-2"><a href="#bind-2" class="headerlink" title="bind()"></a>bind()</h5><p>进入<code>JavaBeanBinder#bind()</code>之后看到继续调用了另一个私有的重载方法</p><figure class="highlight java hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-keyword">private</span> &lt;T&gt; <span class="hljs-function"><span class="hljs-keyword">boolean</span> <span class="hljs-title">bind</span><span class="hljs-params">(BeanPropertyBinder propertyBinder, Bean&lt;T&gt; bean, BeanSupplier&lt;T&gt; beanSupplier)</span> </span>&#123;</span><br><span class="line">    <span class="hljs-keyword">boolean</span> bound = <span class="hljs-keyword">false</span>;</span><br><span class="line">    <span class="hljs-keyword">for</span> (Map.Entry&lt;String, BeanProperty&gt; entry : bean.getProperties().entrySet()) &#123;</span><br><span class="line">        bound |= bind(beanSupplier, propertyBinder, entry.getValue());</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="hljs-keyword">return</span> bound;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>里面会迭代该配置bean中的所有属性, 调试模式下随便取一个来看看:<br></p><figure class="highlight plain hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">0 = &#123;LinkedHashMap$Entry@7470&#125; &quot;name&quot; -&gt; </span><br><span class="line">  key = &quot;name&quot;</span><br><span class="line">  value = &#123;JavaBeanBinder$BeanProperty@7475&#125; </span><br><span class="line">    name = &quot;name&quot;</span><br><span class="line">    declaringClassType = &#123;ResolvableType@7481&#125; &quot;com.example.cache.config.CustomProperties&quot;</span><br><span class="line">    getter = &#123;Method@7482&#125; &quot;public java.lang.String com.example.cache.config.CustomProperties.getName()&quot;</span><br><span class="line">    setter = &#123;Method@7483&#125; &quot;public void com.example.cache.config.CustomProperties.setName(java.lang.String)&quot;</span><br><span class="line">    field = &#123;Field@7484&#125; &quot;private java.lang.String com.example.cache.config.CustomProperties.name&quot;</span><br><span class="line">1 = &#123;LinkedHashMap$Entry@7471&#125; &quot;age&quot; -&gt; </span><br><span class="line">2 = &#123;LinkedHashMap$Entry@7472&#125; &quot;email&quot; -&gt;</span><br></pre></td></tr></table></figure><p></p><p>没错, 配置bean中: 的属性名, Setter和Getter 该有的都有了</p><p>下面来到<code>JavaBeanBinder</code>的最后一个重载方法</p><figure class="highlight java hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="hljs-keyword">private</span> &lt;T&gt; <span class="hljs-function"><span class="hljs-keyword">boolean</span> <span class="hljs-title">bind</span><span class="hljs-params">(BeanSupplier&lt;T&gt; beanSupplier, </span></span></span><br><span class="line"><span class="hljs-function"><span class="hljs-params">                         BeanPropertyBinder propertyBinder, BeanProperty property)</span> </span>&#123;</span><br><span class="line">    String propertyName = property.getName();</span><br><span class="line">    ResolvableType type = property.getType();</span><br><span class="line">    Supplier&lt;Object&gt; value = property.getValue(beanSupplier);</span><br><span class="line">    Annotation[] annotations = property.getAnnotations();</span><br><span class="line">    Object bound = propertyBinder.bindProperty(</span><br><span class="line">        propertyName, Bindable.of(type).withSuppliedValue(value).withAnnotations(annotations)</span><br><span class="line">    );</span><br><span class="line">    <span class="hljs-keyword">if</span> (bound == <span class="hljs-keyword">null</span>) &#123;</span><br><span class="line">        <span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="hljs-keyword">if</span> (property.isSettable()) &#123;</span><br><span class="line">        property.setValue(beanSupplier, bound);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (value == <span class="hljs-keyword">null</span> || !bound.equals(value.get())) &#123;</span><br><span class="line">        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> IllegalStateException(<span class="hljs-string">"No setter found for property: "</span> + property.getName());</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里就比较简单了, 分步拿到了配置Bean属性的定义和值:</p><ul><li><p>field: 即propertyName, e.g. <code>name</code></p></li><li><p>属性类型: type, e.g. <code>java.lang.String</code></p></li><li><p>getter and setter</p><p>e.g.<code>public void com.example.cache.config.CustomProperties.setName(java.lang.String)</code></p></li><li><p>以及调用<code>propertyBinder.bindProperty()</code>拿到了资源属性文件中的属性值<code>bound</code></p><p>该方法的作用前面也提到过(e.g. <code>thank</code> )</p></li></ul><p>然后调用了属性的<code>setValue()</code>方法: 执行<code>property.setValue(beanSupplier, bound);</code></p><p>至此, 看到了调用属性的set方法, 终于可以放心了!</p><p>从<code>ConfigurationPropertiesBindingPostProcessor</code>开始到调用setter结束, 完整的调用栈如下:</p><figure class="highlight plain hljs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">bind:85, JavaBeanBinder &#123;org.springframework.boot.context.properties.bind&#125;</span><br><span class="line">bind:62, JavaBeanBinder &#123;org.springframework.boot.context.properties.bind&#125;</span><br><span class="line">bind:54, JavaBeanBinder &#123;org.springframework.boot.context.properties.bind&#125;</span><br><span class="line">lambda$null$5:341, Binder &#123;org.springframework.boot.context.properties.bind&#125;</span><br><span class="line">apply:-1, 1445225850 &#123;org.springframework.boot.context.properties.bind.Binder$$Lambda$267&#125;</span><br><span class="line">accept:193, ReferencePipeline$3$1 &#123;java.util.stream&#125;</span><br><span class="line">tryAdvance:1351, ArrayList$ArrayListSpliterator &#123;java.util&#125;</span><br><span class="line">forEachWithCancel:126, ReferencePipeline &#123;java.util.stream&#125;</span><br><span class="line">copyIntoWithCancel:498, AbstractPipeline &#123;java.util.stream&#125;</span><br><span class="line">copyInto:485, AbstractPipeline &#123;java.util.stream&#125;</span><br><span class="line">wrapAndCopyInto:471, AbstractPipeline &#123;java.util.stream&#125;</span><br><span class="line">evaluateSequential:152, FindOps$FindOp &#123;java.util.stream&#125;</span><br><span class="line">evaluate:234, AbstractPipeline &#123;java.util.stream&#125;</span><br><span class="line">findFirst:464, ReferencePipeline &#123;java.util.stream&#125;</span><br><span class="line">lambda$bindBean$6:342, Binder &#123;org.springframework.boot.context.properties.bind&#125;</span><br><span class="line">get:-1, 2008619427 &#123;org.springframework.boot.context.properties.bind.Binder$$Lambda$266&#125;</span><br><span class="line">withIncreasedDepth:441, Binder$Context &#123;org.springframework.boot.context.properties.bind&#125;</span><br><span class="line">withBean:427, Binder$Context &#123;org.springframework.boot.context.properties.bind&#125;</span><br><span class="line">access$400:381, Binder$Context &#123;org.springframework.boot.context.properties.bind&#125;</span><br><span class="line">bindBean:339, Binder &#123;org.springframework.boot.context.properties.bind&#125;</span><br><span class="line">bindObject:278, Binder &#123;org.springframework.boot.context.properties.bind&#125;</span><br><span class="line">bind:221, Binder &#123;org.springframework.boot.context.properties.bind&#125;</span><br><span class="line">bind:210, Binder &#123;org.springframework.boot.context.properties.bind&#125;</span><br><span class="line">bind:192, Binder &#123;org.springframework.boot.context.properties.bind&#125;</span><br><span class="line">bind:82, ConfigurationPropertiesBinder &#123;org.springframework.boot.context.properties&#125;</span><br><span class="line">bind:107, ConfigurationPropertiesBindingPostProcessor &#123;org.springframework.boot.context.properties&#125;</span><br><span class="line">postProcessBeforeInitialization:93, ConfigurationPropertiesBindingPostProcessor &#123;org.springframework.boot.context.properties&#125;</span><br></pre></td></tr></table></figure><p>有兴趣debug的可以按照调用栈的顺序由下至上来参考</p><h3 id="五-总结"><a href="#五-总结" class="headerlink" title="五. 总结"></a>五. 总结</h3><p>总体阅读下来, 相较1.x版本封装度更高, 而且代码风格大量java 8中的写法, 确实给阅读带来了些麻烦, 但是优秀的封装能明显感觉到职责更加明确</p><p>顺着源码读下来, 有意略过了很多细节, 例如:</p><ul><li>Validator结合配置Bean怎么进行属性验证的?</li><li>2.x中的宽松绑定约束是在什么地方体现的?</li><li>等等</li></ul><p>重心还是放在了如何发现我们的属性配置Bean以及如何将属性资源文件中的值绑定到属性配置Bean中这两点</p><p>所以只是针对以上两点知其然, 初步窥探了<code>ConfigurationProperties</code>的黑盒</p></div><ul class="post-copyright"><li><strong>本文标题：</strong><a href="https://xiefayang.com/2019/04/01/Spring Boot 配置绑定源码解析/">Spring Boot 2.0源码解析-配置绑定</a></li><li><strong>本文作者：</strong><a href="https://xiefayang.com">i蝸居年華_谢谢谢</a></li><li><strong>本文链接：</strong><a href="https://xiefayang.com/2019/04/01/Spring Boot 配置绑定源码解析/">https://xiefayang.com/2019/04/01/Spring Boot 配置绑定源码解析/</a></li><li><strong>发布时间：</strong>2019-04-01</li><li><strong>版权声明：</strong>本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh" rel="external nofollow" target="_blank">CC BY-NC-SA 4.0</a> 许可协议。转载请注明出处！</li></ul><div class="level is-size-7 is-uppercase"><div class="level-start"><div class="level-item"> <span class="is-size-6 has-text-grey has-mr-7">#</span> <a class="has-link-grey -link" href="/tags/Spring-Boot/">Spring Boot</a>, <a class="has-link-grey -link" href="/tags/Spring全家桶/">Spring全家桶</a>, <a class="has-link-grey -link" href="/tags/源码分析/">源码分析</a></div></div></div><div class="social-share"></div><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/social-share.js@1.0.16/dist/css/share.min.css"><script src="https://cdn.jsdelivr.net/npm/social-share.js@1.0.16/dist/js/social-share.min.js"></script></div></div><div class="card"><div class="card-content"><h3 class="menu-label has-text-centered">坚持原创技术分享，您的支持将鼓励我继续创作！</h3><div class="buttons is-centered"><a class="button is-info donate"><span class="icon is-small"><i class="fab fa-alipay"></i></span> <span>支付宝</span><div class="qrcode"><img src="/images/no2.jpg" alt="支付宝"></div></a><a class="button is-success donate"><span class="icon is-small"><i class="fab fa-weixin"></i></span> <span>微信</span><div class="qrcode"><img src="/images/no.gif" alt="微信"></div></a></div></div></div><div class="card card-transparent"><div class="level post-navigation is-flex-wrap is-mobile"><div class="level-start"><a class="level level-item has-link-grey article-nav-prev" href="/2019/04/02/更换主题icarus/"><i class="level-item fas fa-chevron-left"></i> <span class="level-item">更换icarus主题记录</span></a></div><div class="level-end"> <a class="level level-item has-link-grey article-nav-next" href="/2018/07/16/年中总结与目标OKR/"><span class="level-item">年中总结与目标OKR</span><i class="level-item fas fa-chevron-right"></i></a></div></div></div><div class="card"><div class="card-content"><h3 class="title is-5 has-text-weight-normal">评论</h3><div id="valine-thread" class="content"></div><script src="//cdn1.lncld.net/static/js/3.0.4/av-min.js"></script><script src="/js/Valine.min.js"></script><script>new Valine({el:"#valine-thread",notify:!1,verify:!1,app_id:"yVPV2fAKC1B90Xnh2JjuPEAb-gzGzoHsz",app_key:"1i9DgF7j75Uq6OuM23etjjE9",placeholder:"Just go go, 支持Markdown~"})</script></div></div></div><div class="column is-4-tablet is-4-desktop is-3-widescreen has-order-1 column-left"><div class="card widget"><div class="card-content"><nav class="level"><div class="level-item has-text-centered"><div> <img class="image is-128x128 has-mb-6" src="/images/avatar.png" alt="i蝸居年華_谢谢谢" style="border-radius:50%"><p class="is-size-4 is-block"> i蝸居年華_谢谢谢</p><p class="is-size-6 is-block"> Just for fun</p><p class="is-size-6 is-flex is-flex-center has-text-grey"><i class="fas fa-map-marker-alt has-mr-7"></i> <span>Beijing, China</span></p></div></div></nav><nav class="level is-mobile"><div class="level-item has-text-centered is-marginless"><div><p class="heading"> 文章</p><p class="title has-text-weight-normal"> 49</p></div></div><div class="level-item has-text-centered is-marginless"><div><p class="heading"> 分类</p><p class="title has-text-weight-normal"> 13</p></div></div><div class="level-item has-text-centered is-marginless"><div><p class="heading"> 标签</p><p class="title has-text-weight-normal"> 32</p></div></div></nav><div class="level"> <a class="level-item button is-link is-rounded" href="https://github.com/thank037" target="_blank">关注我</a></div><div class="level is-mobile"> <a class="level-item button is-white is-marginless" target="_blank" title="github" href="https://github.com/thank037" name><img src="/images/github.svg" style="width:20px;height:20px"></a> <a class="level-item button is-white is-marginless" target="_blank" title="gitee" href="https://gitee.com/thank037" name><img src="/images/gitee.svg" style="width:20px;height:20px"></a> <a class="level-item button is-white is-marginless" target="_blank" title="weibo" href="https://weibo.com/u/2139090054" name><img src="/images/weibo.svg" style="width:20px;height:20px"></a> <a class="level-item button is-white is-marginless" target="_blank" title="email" href="mailto:coderthank@163.com" name><img src="/images/email.svg" style="width:20px;height:20px"></a></div></div></div><div class="card widget column-left is-sticky" id="toc"><div class="card-content"><div class="menu"><h3 class="menu-label"> 文章目录</h3><ul class="menu-list"><li> <a class="is-flex" href="#一-前言"><span class="has-mr-6">1</span> <span>一. 前言</span></a></li><li> <a class="is-flex" href="#二-变化"><span class="has-mr-6">2</span> <span>二. 变化</span></a><ul class="menu-list"><li> <a class="is-flex" href="#迁移的变化"><span class="has-mr-6">2.1</span> <span>迁移的变化</span></a></li><li> <a class="is-flex" href="#源码的变化"><span class="has-mr-6">2.2</span> <span>源码的变化</span></a></li></ul></li><li> <a class="is-flex" href="#三-基本使用"><span class="has-mr-6">3</span> <span>三. 基本使用</span></a></li><li> <a class="is-flex" href="#四-源码解析"><span class="has-mr-6">4</span> <span>四. 源码解析</span></a><ul class="menu-list"><li> <a class="is-flex" href="#ConfigurationProperties"><span class="has-mr-6">4.1</span> <span>@ConfigurationProperties</span></a></li><li> <a class="is-flex" href="#ConfigurationPropertiesBindingPostProcessor"><span class="has-mr-6">4.2</span> <span>ConfigurationPropertiesBindingPostProcessor</span></a><ul class="menu-list"><li> <a class="is-flex" href="#afterPropertiesSet"><span class="has-mr-6">4.2.1</span> <span>afterPropertiesSet()</span></a></li><li> <a class="is-flex" href="#关于validator"><span class="has-mr-6">4.2.2</span> <span>关于validator</span></a></li><li> <a class="is-flex" href="#关于propertySources"><span class="has-mr-6">4.2.3</span> <span>关于propertySources</span></a></li><li> <a class="is-flex" href="#postProcessBeforeInitialization"><span class="has-mr-6">4.2.4</span> <span>postProcessBeforeInitialization()</span></a></li><li> <a class="is-flex" href="#bind"><span class="has-mr-6">4.2.5</span> <span>bind()</span></a></li><li> <a class="is-flex" href="#getBinder"><span class="has-mr-6">4.2.6</span> <span>getBinder()</span></a></li></ul></li><li> <a class="is-flex" href="#Binder"><span class="has-mr-6">4.3</span> <span>Binder</span></a><ul class="menu-list"><li> <a class="is-flex" href="#bind-1"><span class="has-mr-6">4.3.1</span> <span>bind()</span></a></li><li> <a class="is-flex" href="#bindObject"><span class="has-mr-6">4.3.2</span> <span>bindObject()</span></a></li><li> <a class="is-flex" href="#bindBean"><span class="has-mr-6">4.3.3</span> <span>bindBean()</span></a></li></ul></li><li> <a class="is-flex" href="#JavaBeanBinder"><span class="has-mr-6">4.4</span> <span>JavaBeanBinder</span></a><ul class="menu-list"><li> <a class="is-flex" href="#bind-2"><span class="has-mr-6">4.4.1</span> <span>bind()</span></a></li></ul></li></ul></li><li> <a class="is-flex" href="#五-总结"><span class="has-mr-6">5</span> <span>五. 总结</span></a></li></ul></div></div></div><div class="column-right-shadow is-hidden-widescreen is-sticky"></div></div></div></div></section><footer class="footer"><div class="container"><div class="level"><div class="level-start has-text-centered-mobile"> <a class="footer-logo is-block has-mb-6" href="/">i蝸居年華_谢谢谢</a><p class="is-size-7"> &copy; 2016—2021 i蝸居年華_谢谢谢&nbsp; 由<a href="http://hexo.io/" target="_blank">Hexo</a> 强力驱动<br> <span id="busuanzi_container_site_uv">您是第<span id="busuanzi_value_site_uv">0</span>个访客</span></p></div><div class="level-end"><div class="field has-addons is-flex-center-mobile has-mt-5-mobile is-flex-wrap is-flex-middle"><p class="control"><a class="button is-white is-large" target="_blank" title="Creative Commons" href="https://creativecommons.org/"><i class="fab fa-creative-commons"></i></a></p><p class="control"><a class="button is-white is-large" target="_blank" title="Attribution 4.0 International" href="https://creativecommons.org/licenses/by/4.0/"><i class="fab fa-creative-commons-by"></i></a></p><p class="control"><a class="button is-white is-large" target="_blank" title="Download on GitHub" href="https://github.com/thank037"><i class="fab fa-github"></i></a></p></div></div></div></div></footer><script src="https://cdn.jsdelivr.net/npm/jquery@3.3.1/dist/jquery.min.js"></script><script src="https://cdn.jsdelivr.net/npm/moment@2.22.2/min/moment-with-locales.min.js"></script><script>moment.locale("zh-CN")</script><script src="/js/animation.js"></script><script src="https://cdn.jsdelivr.net/npm/lightgallery@1.6.8/dist/js/lightgallery.min.js" defer="defer"></script><script src="https://cdn.jsdelivr.net/npm/justifiedGallery@3.7.0/dist/js/jquery.justifiedGallery.min.js" defer="defer"></script><script src="/js/gallery.js" defer="defer"></script><div id="outdated"><h6>Your browser is out-of-date!</h6><p>Update your browser to view this website correctly. <a id="btnUpdateBrowser" href="http://outdatedbrowser.com/">Update my browser now</a></p><p class="last"><a href="#" id="btnCloseUpdateBrowser" title="Close">&times;</a></p></div><script src="https://cdn.jsdelivr.net/npm/outdatedbrowser@1.1.5/outdatedbrowser/outdatedbrowser.min.js" defer="defer"></script><script>document.addEventListener("DOMContentLoaded",function(){outdatedBrowser({bgColor:"#f25648",color:"#ffffff",lowerThan:"flex"})})</script><script src="https://cdn.jsdelivr.net/npm/mathjax@2.7.5/unpacked/MathJax.js?config=TeX-MML-AM_CHTML" defer="defer"></script><script>document.addEventListener("DOMContentLoaded",function(){MathJax.Hub.Config({"HTML-CSS":{matchFontHeight:!1},SVG:{matchFontHeight:!1},CommonHTML:{matchFontHeight:!1},tex2jax:{inlineMath:[["$","$"],["\\(","\\)"]]}})})</script><a id="back-to-top" title="回到顶端" href="javascript:;"><i class="fas fa-chevron-up"></i></a><script src="/js/back-to-top.js" defer="defer"></script><script src="https://cdn.jsdelivr.net/npm/clipboard@2.0.4/dist/clipboard.min.js" defer="defer"></script><script src="/js/clipboard.js" defer="defer"></script><script src="/js/main.js" defer="defer"></script><div class="searchbox ins-search"><div class="searchbox-container ins-search-container"><div class="searchbox-input-wrapper"> <input type="text" class="searchbox-input ins-search-input" placeholder="全文搜索站内文章~"><span class="searchbox-close ins-close ins-selectable"><i class="fa fa-times-circle"></i></span></div><div class="searchbox-result-wrapper ins-section-wrapper"><div class="ins-section-container"></div></div></div></div><script>window.INSIGHT_CONFIG={TRANSLATION:{POSTS:"文章",PAGES:"页面",CATEGORIES:"分类",TAGS:"标签",UNTITLED:"(无标题)"},CONTENT_URL:"/content.json"}</script><script src="/js/insight.js" defer="defer"></script><link rel="stylesheet" href="/css/search.css"><link rel="stylesheet" href="/css/insight.css"><script src="/live2dw/lib/L2Dwidget.min.js?094cbace49a39548bed64abff5988b05"></script><script>L2Dwidget.init({pluginRootPath:"live2dw/",pluginJsPath:"lib/",pluginModelPath:"assets/",tagMode:!1,debug:!1,model:{jsonPath:"/live2dw/assets/hijiki.model.json"},display:{position:"right",width:150,height:300,mobile:{show:!0},react:{opacity:.7}},log:!1})</script></body></html>